package wsi.subgraph.tree;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import org.rygh.semantic.model.ex.FactSet;
import org.rygh.semantic.model.rdf.Entity;
import org.rygh.semantic.model.rdf.Fact;
import org.rygh.semantic.model.rdf.Predicate;

public class EntityTreeLoader {
	final Logger log = Logger.getLogger(getClass().getName());

	private final EntityTree tree = new EntityTree();
	private final FactSet facts;
	private final Entity centerEntity;

	public EntityTreeLoader(Entity centerEntity, FactSet facts) {
		this.centerEntity = centerEntity;
		this.facts = facts;

		assert centerEntity != null;
	}

	public EntityTree load() {
		// System.out.println("  begin to load entity tree, fact size "
		// + facts.size());

		List<EntityLeaf> toadd = new ArrayList<>();
		List<EntityLeaf> leaves = new ArrayList<>();
		FactSet toremove = new FactSet();

		EntityLeaf root = new EntityLeaf(tree.getRoot(), new EntityObject(
				centerEntity, null));

		/*
		 * we cannot use body string as key here. otherwise, consider
		 * y:Enya_(album), the body string is Enya, which would be erroneously
		 * mapped to the name property.
		 * 
		 * on the other hand, we also have to match song/album names, which
		 * often come with suffix. this is a dilemma we need to address.
		 */
		// tree.putNode(entity.getStringWithoutPrefix(), root);
		leaves.add(root);

		EntityLeaf new_node, leaf;
		Entity entity;
		Predicate pred;
		String val;
		int oldsize = 0;

		while (!facts.isEmpty()) {
			toadd.clear();

			if (oldsize == facts.size()) {

				log.info(String.format(
						"Cannot load entity tree:\n%s \nsize %d, center %s",
						facts.toString(), oldsize,
						centerEntity.toLexicalString()));

				return null;
			}

			oldsize = facts.size();

			for (EntityLeaf i : leaves) {
				entity = i.getValue().getValue();

				if (entity.isLiteral())
					continue;

				val = entity.getValue();
				toremove.clear();

				for (Fact f : facts) {
					leaf = null;

					if (f.getSubject().getValue().equals(val)) {
						pred = new Predicate(f.getPredicate());
						pred.setInverse(false);

						leaf = new EntityLeaf(i, new EntityObject(
								f.getObject(), pred));

					} else if (f.getObject().getValue().equals(val)) {
						pred = new Predicate(f.getPredicate());
						pred.setInverse(true);

						leaf = new EntityLeaf(i, new EntityObject(
								f.getSubject(), pred));
					}

					if (leaf != null) {
						toadd.add(leaf);
						toremove.add(f);
					}
				}

				facts.remove(toremove);
			}

			leaves.addAll(toadd);
		}

		/*
		 * for example, y:song y:created- y:artist_1, y:song y:wrote-
		 * y:artist_1, we have to duplicate y:artist_1 subtree in all such
		 * places.
		 */

		boolean duplicated = true;

		while (duplicated) {
			// this while is very important, saves a lot of time

			duplicated = false;

			leaves = tree.getAllLeaves();

			for (EntityLeaf l : leaves) {
				assert l.isLeaf();

				if (l.getValue().getValue().isLiteral())
					continue;

				List<EntityLeaf> nodes = tree.findNodes(l.getValue());

				EntityLeaf the_node = null;

				for (EntityLeaf n : nodes)
					if (!n.isLeaf()) {
						the_node = n;
						break;
					}

				if (the_node == null) {
					// log.info(String.format(
					// "[warn] cannot find a subtree for %s", l.getValue()
					// .getValue()));
					continue;
				}

				// System.out.printf("duplicate node %s at %s -> %s\n", l
				// .getValue().getValue(), the_node.getValue().getValue(),
				// the_node.getParent().getValue().getValue());

				new_node = the_node.duplicateAt(l.getParent());
				new_node.getValue().setPredicate(l.getValue().getPredicate());
				l.getParent().getChildren().remove(l);

				duplicated = true;
			}
		}

		// System.out.println("Tree " + tree.toString());

		leaves = tree.getAllLeaves();

		for (EntityLeaf l : leaves) {
			entity = l.getValue().getValue();

			if (!entity.isLiteral()) {
				// there are cases some entity nodes do not have literal
				// endings.

				// log.info("Incomplete entity node " +
				// l.getValue().getValue());

				l.removeFromParent();
				continue;
			}

			tree.putNode(entity.getStringWithoutPrefix(), l);
		}

		if (tree.getConcreteRoot() == null || tree.getConcreteRoot().isLeaf()) {
			log.info("[warn] empty entity tree for " + centerEntity);
			return null;
		}

		// System.out.println("entityTree " + tree);

		// System.out.println("  end loading entity tree.");

		return tree;
	}
}
